package onez.project.core;

import onez.project.common.config.DynamicConfigManager;
import onez.project.common.config.ServiceDefinition;
import onez.project.common.config.ServiceInstance;
import onez.project.common.utils.NetUtils;
import onez.project.common.utils.TimeUtil;

import onez.project.config.center.api.ConfigCenter;
import onez.project.register.center.api.RegisterCenter;
import onez.project.register.center.api.RegisterCenterListener;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;


import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import static onez.project.common.constant.BasicConst.COLON_SEPARATOR;


@Slf4j
public class Bootstrap {
    public static void main(String[] args) {
        //加载网关核心静态配置
        Config config = ConfigLoader.getInstance().load(args);
        log.info(String.valueOf(config.getPort()));

        //插件初始化
        //配置中心管理器初始化，连接配置中心，监听配置的新增、修改、删除
        ServiceLoader<ConfigCenter> serviceLoader = ServiceLoader.load(ConfigCenter.class);
        final ConfigCenter configCenter = serviceLoader.findFirst().orElseThrow(() -> {
            log.error("not found ConfigCenter impl");
            return new RuntimeException("not found ConfigCenter impl");
        });

        // 从配置中心获取配置
        configCenter.init(config.getRegistryAddress(), config.getEnv());
        configCenter.subscribeRulesChange(rules -> DynamicConfigManager.getInstance().putAllRule(rules));


        //启动容器
        Container container = new Container(config);
        container.start();

        //连接注册中心，将注册中心的实例加载到本地
        final RegisterCenter registerCenter = registerAndSubscribe(config);

        //服务优雅关机
        //收到kill信号时调用
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            registerCenter.deregister(buildGatewayServiceDefinition(config), buildGatewayServiceInstance(config));//从nacos注销
            container.shutdown();
        }));
    }

    /**
     * 向注册中心注册并订阅服务
     * 此方法首先加载注册中心的实现类，然后初始化注册中心
     * 接着，它构建网关的服务定义和服务实例，并进行注册
     * 最后，它订阅所有服务，以便在服务或实例发生变化时更新配置
     *
     * @param config 系统配置信息，包含注册中心地址和环境信息等
     * @return 返回初始化并注册成功的注册中心实例
     * @throws RuntimeException 如果没有找到注册中心的实现类，则抛出运行时异常
     */
    private static RegisterCenter registerAndSubscribe(Config config) {
        //加载注册中心实现类
        ServiceLoader<RegisterCenter> serviceLoader = ServiceLoader.load(RegisterCenter.class);
        //获取第一个注册中心实现类，如果不存在，则抛出异常
        final RegisterCenter registerCenter = serviceLoader.findFirst().orElseThrow(() -> {
            log.error("not found RegisterCenter impl");
            return new RuntimeException("not found RegisterCenter impl");
        });
        //初始化注册中心
        registerCenter.init(config.getRegistryAddress(), config.getEnv());

        //构造网关服务定义和服务实例
        ServiceDefinition serviceDefinition = buildGatewayServiceDefinition(config);
        ServiceInstance serviceInstance = buildGatewayServiceInstance(config);

        //注册
        registerCenter.register(serviceDefinition, serviceInstance);

        //订阅
        registerCenter.subscribeAllServices((serviceDefinition1, serviceInstanceSet) -> {
            log.info("refresh service and instance: {} {}", serviceDefinition1.getUniqueId(), JSON.toJSON(serviceInstanceSet));
            DynamicConfigManager manager = DynamicConfigManager.getInstance();
            //将这次变更事件影响之后的服务实例再次添加到对应的服务实例集合
            manager.addServiceInstance(serviceDefinition1.getUniqueId(), serviceInstanceSet);
            //修改发生对应的服务定义
            manager.putServiceDefinition(serviceDefinition1.getUniqueId(), serviceDefinition1);
        });
        return registerCenter;
    }

    private static ServiceInstance buildGatewayServiceInstance(Config config) {
        String localIp = NetUtils.getLocalIp();
        int port = config.getPort();
        ServiceInstance serviceInstance = new ServiceInstance();
        serviceInstance.setServiceInstanceId(localIp + COLON_SEPARATOR + port);
        serviceInstance.setIp(localIp);
        serviceInstance.setPort(port);
        serviceInstance.setRegisterTime(TimeUtil.currentTimeMillis());
        return serviceInstance;
    }

    private static ServiceDefinition buildGatewayServiceDefinition(Config config) {
        ServiceDefinition serviceDefinition = new ServiceDefinition();
        serviceDefinition.setInvokerMap(Map.of());
        serviceDefinition.setUniqueId(config.getApplicationName());
        serviceDefinition.setServiceId(config.getApplicationName());
        serviceDefinition.setEnvType(config.getEnv());
        return serviceDefinition;
    }
}
